本节代码对应 GitHub 分支: chapter7

仓库传送门 (opens new window)

# 交互逻辑实现

JS 交互主要是滑动屏幕时的逻辑,现在有了 scroll 基础组件,我们可以直接写在 onScroll 的回调中。

<Scroll onScroll={handleScroll} ref={songScroll}>
//...

这里面需要一些 DOM 操作,我们先把 DOM 对象取出来。

import { HEADER_HEIGHT } from "./../../api/config";
const handleScroll = pos => {
    let height = initialHeight.current;
    const newY = pos.y;
    const imageDOM = imageWrapper.current;
    const buttonDOM = collectButton.current;
    const headerDOM = header.current;
    const layerDOM = layer.current;
    const minScrollY = -(height - OFFSET) + HEADER_HEIGHT;

    // 指的是滑动距离占图片高度的百分比
    const percent = Math.abs (newY /height);

}

说明:在歌手页的布局中,歌单列表其实是没有自己的背景的,layerDOM 其实是起一个遮罩的作用,给歌单内容提供白色背景 因此在处理的过程中,随着内容的滚动,遮罩也跟着移动。

滑动主要分三种情况:

  1. 处理往下拉的情况,效果:图片放大,按钮跟着偏移
if (newY > 0) {
  imageDOM.style ["transform"] = `scale (${1 + percent})`;
  buttonDOM.style ["transform"] = `translate3d (0, ${newY}px, 0)`;
  layerDOM.style.top = `${height - OFFSET + newY}px`;
}
  1. 往上滑动,但是遮罩还没超过 Header 部分
else if (newY >= minScrollY) {
  layerDOM.style.top = `${height - OFFSET - Math.abs (newY)}px`;
  // 这时候保证遮罩的层叠优先级比图片高,不至于被图片挡住
  layerDOM.style.zIndex = 1;
  imageDOM.style.paddingTop = "75%";
  imageDOM.style.height = 0;
  imageDOM.style.zIndex = -1;
  // 按钮跟着移动且渐渐变透明
  buttonDOM.style ["transform"] = `translate3d (0, ${newY}px, 0)`;
  buttonDOM.style ["opacity"] = `${1 - percent * 2}`;
}
  1. 往上滑动,但是遮罩超过 Header 部分
else if (newY < minScrollY) {
  // 往上滑动,但是超过 Header 部分
  layerDOM.style.top = `${HEADER_HEIGHT - OFFSET}px`;
  layerDOM.style.zIndex = 1;
  // 防止溢出的歌单内容遮住 Header
  headerDOM.style.zIndex = 100;
  // 此时图片高度与 Header 一致
  imageDOM.style.height = `${HEADER_HEIGHT}px`;
  imageDOM.style.paddingTop = 0;
  imageDOM.style.zIndex = 99;
}

现在终于可以达到一个比较好的交互效果了。但是别忘了,handleScroll 作为一个传给子组件的方法,我们需要用 useCallback 进行包裹,防止不必要的重渲染。

const handleScroll = useCallback (pos => {
  // 具体代码
}, []);
阅读全文